package no.ffi.fca.certstore;

import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import no.ffi.fca.Settings;
import no.ffi.fca.db.DBHelper;
import no.ffi.fca.db.DBHelper.DBHelperException;
import no.ffi.fca.db.result.CertificateList.Certificate;
import no.ffi.gismoidm.certstore.CertificateFileOperations;
import no.ffi.gismoidm.certstore.CertificateService;
import no.ffi.gismoidm.utils.CertificateNotValidatedException;

public class DatabaseStorageService implements CertificateService {
    
    KeyStore anchors;
    
    public DatabaseStorageService(X509Certificate trustAnchor) throws Exception {
        System.out.println("db created...");
        
        // Establish the trust anchor (given in a PEM file)
        anchors = KeyStore.getInstance(KeyStore.getDefaultType());
        anchors.load(null);
        if (trustAnchor != null) // TODO remove later
            anchors.setCertificateEntry("CA",trustAnchor);
        
//        // Check if there is a trust root for TPM devices
//        try {
//            X509Certificate tpmroot = CertificateFileOperations.loadPemCert(certDirectory + "TPMrootcert.pem");
//            anchors.setCertificateEntry("TPM",tpmroot);
//        } catch (IOException e1) { } // No tpm root cert available
//          catch (Exception e2) { e2.printStackTrace(); }
        
    }

    public X509Certificate findDNcert(X500Principal xp) {
        Certificate c;
        DBHelper db;
        try {
            db = new DBHelper();
        } catch (DBHelperException ex) {
            System.out.println(ex);
            return null;
        }
        c = db.getCertificate(xp);
        db.close();
        if (c == null) {
            return null;
        }
        String filename = Settings.PATH_CA_SIGNED + c.serial + ".pem";
        X509Certificate cert;
        try {
            cert = CertificateFileOperations.loadPemCert(filename);
        } catch (Exception ex) {
            return null;
        }
        return cert;
    }
    
    public X509Certificate findSerialCert(String subjectHash, String serialNumber) {
        Certificate c;
        String filename;
        DBHelper db = null;
        try {
            db = new DBHelper();
            // serialNumber starts with a _ if it's a AIKcert token
            if (serialNumber.startsWith("_")) {
                serialNumber = serialNumber.substring(1);
                
                // serialNumber is in reality the token. 
                c = db.getAIKCertificate(serialNumber);
                
                // c.serial is the actual serial
                filename = Settings.PATH_CA_TPM_SIGNED + c.serial + ".pem";
                
            }
            else {
                c = db.getCertificate(serialNumber);
                filename = Settings.PATH_CA_SIGNED + c.serial + ".pem";
            }
        } catch (DBHelperException ex) {
            System.out.println(ex);
            return null;
        } finally {
            if (db != null) db.close();
        }
        if (c == null) return null;
        X509Certificate cert;
        try {
            cert = CertificateFileOperations.loadPemCert(filename);
        } catch (Exception ex) {
            return null;
        }
        if (subjectHash.equals(c.hash_old)) return cert;
        return null;
    }
    
    @Override
    public X509Certificate retrieveAndValidate(X500Principal dn) throws Exception {
        // First, build a certificate path up to the first self-signed certificate
        System.out.println("Retrieve and validate for " + dn.getName());
        ArrayList certPath = new ArrayList();
        X500Principal dn1, dn0 = dn;
        X509Certificate c0=null;
        while (true) { // Follow signature chain
            try {
                c0 = findDNcert(dn0);
            } catch (Exception e) 
                {throw new CertificateNotValidatedException("Certificate file read error:" + e.getMessage());}
            if (c0==null) break;
            dn1 = c0.getIssuerX500Principal();
            if (dn1.equals(dn0)) break; // Self-signed
            certPath.add(c0);
            dn0 = dn1;
        }
        if (certPath.size()>0 && validate(certPath)) 
            return (X509Certificate)certPath.get(0);
        else throw new CertificateNotValidatedException("Certificate to " + dn + " could not be validated");
    }
    
    @Override
    public X509Certificate retrieveAndValidate(String subjectHash, String serialNumber) throws Exception {
        // First, build a certificate path up to the first self-signed certificate
        System.out.println("Retrieve and validate for " + subjectHash + ":" + serialNumber);
        ArrayList certPath = new ArrayList();
        X509Certificate c0 = null;
        try {
            c0 = findSerialCert(subjectHash,serialNumber);
        } catch (Exception e) 
                {throw new CertificateNotValidatedException("Certificate file read error:" + e.getMessage());}
        certPath.add(c0);
        X500Principal dn1, dn0 = c0.getIssuerX500Principal();

        while (true) { // Follow signature chain
            try {
                 c0 = findDNcert(dn0);
            } catch (Exception e) 
                {throw new CertificateNotValidatedException("Certificate file read error:" + e.getMessage());}
            if (c0==null) break;
            dn1 = c0.getIssuerX500Principal();
            if (dn1.equals(dn0)) break; // Self-signed
            certPath.add(c0);
            dn0 = dn1;
        }
        if (certPath.size()>0 && validate(certPath)) 
            return (X509Certificate)certPath.get(0);
        else throw new CertificateNotValidatedException("Certificate could not be validated");
    }

        
    // Now create a certpath validator to validate the certpath
    private boolean validate(List<X509Certificate> certPath) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            PKIXParameters pp = new PKIXParameters(anchors);
            pp.setRevocationEnabled(false);
            pp.setPolicyQualifiersRejected(false);
            CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
            CertPath cp = cf.generateCertPath(certPath);
            cpv.validate(cp, pp);
        } catch (GeneralSecurityException gse) {
            System.out.println("CertPathValidation exception: " + gse);
            return false;
        }
        return true;
    }
}
